/*
 * Copyright 2017 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import <Foundation/Foundation.h>

#import <XCTest/XCTest.h>

#import "FIRAdditionalUserInfo_Internal.h"
#import "FIRAuth_Internal.h"
#import "FIRAuthErrorUtils.h"
#import "FIRAuthBackend.h"
#import "FIRAuthGlobalWorkQueue.h"
#import "FIRAuthOperationType.h"
#import "FIRAuthTokenResult.h"
#import "FIREmailAuthProvider.h"
#import "FIREmailLinkSignInResponse.h"
#import "FIRFacebookAuthProvider.h"
#import "FIRGetAccountInfoRequest.h"
#import "FIRGetAccountInfoResponse.h"
#import "FIRGoogleAuthProvider.h"
#import "FIRSecureTokenService.h"
#import "FIRSecureTokenRequest.h"
#import "FIRSecureTokenResponse.h"
#import "FIRSetAccountInfoRequest.h"
#import "FIRSetAccountInfoResponse.h"
#import "FIRSignUpNewUserResponse.h"
#import "FIRUser_Internal.h"
#import "FIRUserInfo.h"
#import "FIRUserMetadata.h"
#import "FIRVerifyAssertionResponse.h"
#import "FIRVerifyAssertionRequest.h"
#import "FIRVerifyPasswordRequest.h"
#import "FIRVerifyPasswordResponse.h"
#import "FIRVerifyPhoneNumberRequest.h"
#import "FIRVerifyPhoneNumberResponse.h"
#import "FIRApp+FIRAuthUnitTests.h"
#import "OCMStubRecorder+FIRAuthUnitTests.h"
#import <OCMock/OCMock.h>

#if TARGET_OS_IOS
#import "Phone/FIRPhoneAuthCredential_Internal.h"
#import "FIRPhoneAuthProvider.h"
#endif

NS_ASSUME_NONNULL_BEGIN

/** @var kAPIKey
    @brief The fake API key.
 */
static NSString *const kAPIKey = @"FAKE_API_KEY";

/** @var kAccessToken
    @brief The fake access token.
 */
static NSString *const kAccessToken = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZDBhODBiYzh"
    "jM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmItc2EtdXBncm"
    "FkZWQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIs"
    "InN1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2"
    "VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6"
    "WyJhdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m"
    "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC"
    "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN"
    "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW"
    "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";

/** @var kAccessTokenWithBase64URLCharacters
    @brief The fake access where the AUD value is "??????????>>>>>>>>>>" and the email value is
        ">>>>>>>>????????@gmail.com".
 */
static NSString *const kAccessTokenWithBase64URLCharacter = @"ey?hbGciOimnuzI1NiIsImtpZCI6ImY1YjE4M"
    "jc2YTQ4NjYxZDBhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2ds"
    "ZS5jb20vZmItc2EtdXBncmFkZWQiLCJhdWQiOiI_Pz8_Pz8_Pz8_Pj4-Pj4-Pj4-PiIsImF1dGhfdGltZSI6MTUyMjM2MD"
    "U0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIsInN1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1"
    "MjIzNjQxNTcsImVtYWlsIjoiPj4-Pj4-Pj4_Pz8_Pz8_P0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm"
    "ZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsiYXVuaXRlc3R1c2VyQGdtYWlsLmNvbSJdfSwic2lnbl9pbl9w"
    "cm92aWRlciI6IlBhc3N3b3JkIn19.WFQqSrpVnxx7mUrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-"
    "4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt"
    "1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjNhHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7"
    "jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbWlddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";

/** @var kbase64URLEncodedEmail
    @brief The fake email address with a value containing non-valid base64 encoded characters.
    @remarks This is used to ensure that the token parser is able to handle base64 URL enconded
        strings.
 */
static NSString *const kbase64URLEncodedEmail = @">>>>>>>>????????@gmail.com";

/** @var kbase64URLEncodedAUD
    @brief The fake AUD with a value containing non-valid base64 encoded characters.
    @remarks This is used to ensure that the token parser is able to handle base64 URL enconded
        strings.
 */
static NSString *const kbase64URLEncodedAUD = @"??????????>>>>>>>>>>";

/** @var kAccessTokenLength415
    @brief The fake access token with 415 characters in the claims potion of the token.
 */
static NSString *const kAccessTokenLength415 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD"
    "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd"
    "CIsImF1ZCI6InRlc3RfYXVkIiwiYXV0aF90aW1lIjoxNTIyMzYwNTQ5LCJ1c2VyX2lkIjoidGVzdF91c2VyX2lkIiwic3V"
    "iIjoidGVzdF9zdWIiLCJpYXQiOjE1MjIzNjA1NTcsImV4cCI6MTUyMjM2NDE1NywiZW1haWwiOiJhdW5pdGVzdHVzZXJAZ"
    "21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImF"
    "1bml0ZXN0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ=.WFQqSrpVnxx7m"
    "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC"
    "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN"
    "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW"
    "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";

/** @var kAccessTokenLength416
    @brief The fake access token with 416 characters in the claims potion of the token.
 */
static NSString *const kAccessTokenLength416 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD"
    "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd"
    "DIiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIsInN"
    "1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2VyQ"
    "GdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ"
    "hdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m"
    "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC"
    "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN"
    "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW"
    "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";

/** @var kAccessTokenLength4523
    @brief The fake access token with 523 characters in the claims potion of the token.
 */
static NSString *const kAccessTokenLength523 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD"
    "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd"
    "DQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZF81NDM"
    "yIiwic3ViIjoidGVzdF9zdWIiLCJpYXQiOjE1MjIzNjA1NTcsImV4cCI6MTUyMjM2NDE1OSwiZW1haWwiOiJhdW5pdGVzd"
    "HVzZXI0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWl"
    "sIjpbImF1bml0ZXN0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ=.WFQqSrpVn"
    "xx7mUrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpO"
    "vqKC3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH"
    "7jjNhHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqV"
    "kPbWlddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";

/** @var kNewAccessToken
    @brief A new value for the fake access token.
 */
static NSString *const kNewAccessToken = @"NEW_ACCESS_TOKEN";

/** @var kAccessTokenValidInterval
    @brief The time to live for the fake access token.
 */
static const NSTimeInterval kAccessTokenTimeToLive = 60 * 60;

/** @var kRefreshToken
    @brief The fake refresh token.
 */
static NSString *const kRefreshToken = @"REFRESH_TOKEN";

/** @var kLocalID
    @brief The fake local user ID.
 */
static NSString *const kLocalID = @"LOCAL_ID";

/** @var kAnotherLocalID
    @brief The fake local ID of another user.
 */
static NSString *const kAnotherLocalID = @"ANOTHER_LOCAL_ID";

/** @var kGoogleIDToken
    @brief The fake ID token from Google Sign-In.
 */
static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN";

/** @var kFacebookIDToken
    @brief The fake ID token from Facebook Sign-In. Facebook provider ID token is always nil.
 */
static NSString *const kFacebookIDToken = nil;

/** @var kGoogleAccessToken
    @brief The fake access token from Google Sign-In.
 */
static NSString *const kGoogleAccessToken = @"GOOGLE_ACCESS_TOKEN";

/** @var kFacebookAccessToken
    @brief The fake access token from Facebook Sign-In.
 */
static NSString *const kFacebookAccessToken = @"FACEBOOK_ACCESS_TOKEN";

/** @var kEmail
    @brief The fake user email.
 */
static NSString *const kEmail = @"user@company.com";

/** @var kPhoneNumber
    @brief The fake user phone number.
 */
static NSString *const kPhoneNumber = @"12345658";

/** @var kTemporaryProof
    @brief The fake temporary proof.
 */
static NSString *const kTemporaryProof = @"12345658";

/** @var kNewEmail
    @brief A new value for the fake user email.
 */
static NSString *const kNewEmail = @"newuser@company.com";

/** @var kUserName
    @brief The fake user name.
 */
static NSString *const kUserName = @"User Doe";

/** @var kNewDisplayName
    @brief A new value for the fake user display name.
 */
static NSString *const kNewDisplayName = @"New User Doe";

/** @var kPhotoURL
    @brief The fake user profile image URL string.
 */
static NSString *const kPhotoURL = @"https://host.domain/image";

/** @var kNewPhotoURL
    @brief A new value for the fake user profile image URL string..
 */
static NSString *const kNewPhotoURL = @"https://host.domain/new/image";

/** @var kFakePassword
    @brief The fake user password.
 */
static NSString *const kFakePassword = @"123456";

/** @var kNewPassword
    @brief The fake new user password.
 */
static NSString *const kNewPassword = @"1234567";

/** @var kPasswordHash
    @brief The fake user password hash.
 */
static NSString *const kPasswordHash = @"UkVEQUNURUQ=";

/** @var kGoogleUD
    @brief The fake user ID under Google Sign-In.
 */
static NSString *const kGoogleID = @"GOOGLE_ID";

/** @var kGoogleEmail
    @brief The fake user email under Google Sign-In.
 */
static NSString *const kGoogleEmail = @"user@gmail.com";

/** @var kGoogleDisplayName
    @brief The fake user display name under Google Sign-In.
 */
static NSString *const kGoogleDisplayName = @"Google Doe";

/** @var kEmailDisplayName
    @brief The fake user display name for email password user.
 */
static NSString *const kEmailDisplayName = @"Email Doe";

/** @var kFacebookDisplayName
    @brief The fake user display name under Facebook Sign-In.
 */
static NSString *const kFacebookDisplayName = @"Facebook Doe";

/** @var kGooglePhotoURL
    @brief The fake user profile image URL string under Google Sign-In.
 */
static NSString *const kGooglePhotoURL = @"https://googleusercontents.com/user/profile";

/** @var kFacebookID
    @brief The fake user ID under Facebook Login.
 */
static NSString *const kFacebookID = @"FACEBOOK_ID";

/** @var kFacebookEmail
    @brief The fake user email under Facebook Login.
 */
static NSString *const kFacebookEmail = @"user@facebook.com";

/** @var kVerificationCode
    @brief Fake verification code used for testing.
 */
static NSString *const kVerificationCode = @"12345678";

/** @var kVerificationID
    @brief Fake verification ID for testing.
 */
static NSString *const kVerificationID = @"55432";

/** @var kUserArchiverKey
    @brief The key used to archive and unarchive the user object for test NSSecureCoding.
 */
static NSString *const kUserArchiverKey = @"userArchiverKey";

/** @var kCreationDateInSeconds
    @brief The fake creation date.
 */
static NSTimeInterval const kCreationDateTimeIntervalInSeconds = 1505858500;

/** @var kLastSignInDateTimeIntervalInSeconds
    @brief The fake last sign in date date.
 */
static NSTimeInterval const  kLastSignInDateTimeIntervalInSeconds = 1505858583;

/** @var kExpectationTimeout
    @brief The maximum time waiting for expectations to fulfill.
 */
static const NSTimeInterval kExpectationTimeout = 2;

/** @extention FIRSecureTokenService
    @brief Extends the FIRSecureTokenService class to expose one private method for testing only.
 */
@interface FIRSecureTokenService ()

/** @fn hasValidAccessToken
    @brief private method exposed so it can be mocked to prevent the fake expiration date from
        affecting the the unit tests.
 */
- (BOOL)hasValidAccessToken;
@end

/** @class FIRUserTests
    @brief Tests for @c FIRUser .
 */
@interface FIRUserTests : XCTestCase
@end
@implementation FIRUserTests {

  /** @var _mockBackend
      @brief The mock @c FIRAuthBackendImplementation .
   */
  id _mockBackend;
}

/** @fn googleProfile
    @brief The fake user profile under additional user data in @c FIRVerifyAssertionResponse.
 */
+ (NSDictionary *)googleProfile {
  static NSDictionary *kGoogleProfile = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    kGoogleProfile = @{
      @"email": kGoogleEmail,
      @"given_name": @"User",
      @"family_name": @"Doe"
    };
  });
  return kGoogleProfile;
}

- (void)setUp {
  [super setUp];
  _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  [FIRAuthBackend setBackendImplementation:_mockBackend];
  [FIRApp resetAppForAuthUnitTests];
}

- (void)tearDown {
  [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
  [super tearDown];
}

#pragma mark - Tests

/** @fn testUserPropertiesAndNSSecureCoding
    @brief Tests properties of the @c FIRUser instance before and after being
        serialized/deserialized.
 */
- (void)testUserPropertiesAndNSSecureCoding {
  // Mock auth provider user info for email/password for GetAccountInfo.
  id mockPasswordUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
  OCMStub([mockPasswordUserInfo providerID]).andReturn(FIREmailAuthProviderID);
  OCMStub([mockPasswordUserInfo federatedID]).andReturn(kEmail);
  OCMStub([mockPasswordUserInfo email]).andReturn(kEmail);

  // Mock auth provider user info from Google for GetAccountInfo.
  id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
  OCMStub([mockGoogleUserInfo providerID]).andReturn(FIRGoogleAuthProviderID);
  OCMStub([mockGoogleUserInfo displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGoogleUserInfo photoURL]).andReturn([NSURL URLWithString:kGooglePhotoURL]);
  OCMStub([mockGoogleUserInfo federatedID]).andReturn(kGoogleID);
  OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail);

  // Mock auth provider user info from Facebook for GetAccountInfo.
  id mockFacebookUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
  OCMStub([mockFacebookUserInfo providerID]).andReturn(FIRFacebookAuthProviderID);
  OCMStub([mockFacebookUserInfo federatedID]).andReturn(kFacebookID);
  OCMStub([mockFacebookUserInfo email]).andReturn(kFacebookEmail);

  #if TARGET_OS_IOS
  // Mock auth provider user info from Phone auth provider for GetAccountInfo.
  id mockPhoneUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
  OCMStub([mockPhoneUserInfo providerID]).andReturn(FIRPhoneAuthProviderID);
  OCMStub([mockPhoneUserInfo phoneNumber]).andReturn(kPhoneNumber);
  #endif

  // Mock the root user info object for GetAccountInfo.
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser emailVerified]).andReturn(YES);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser photoURL]).andReturn([NSURL URLWithString:kPhotoURL]);
  OCMStub([mockGetAccountInfoResponseUser creationDate]).
      andReturn([NSDate dateWithTimeIntervalSince1970:kCreationDateTimeIntervalInSeconds]);
  OCMStub([mockGetAccountInfoResponseUser lastLoginDate]).
      andReturn([NSDate dateWithTimeIntervalSince1970:kLastSignInDateTimeIntervalInSeconds]);
  NSArray *providerUserInfos = @[
    #if TARGET_OS_IOS
    mockPhoneUserInfo,
    #endif
    mockPasswordUserInfo,
    mockGoogleUserInfo,
    mockFacebookUserInfo
  ];
  OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(providerUserInfos);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    // Verify FIRUserInfo properties on FIRUser itself.
    XCTAssertEqualObjects(user.providerID, @"Firebase");
    XCTAssertEqualObjects(user.uid, kLocalID);
    XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
    XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kPhotoURL]);
    XCTAssertEqualObjects(user.email, kEmail);
    XCTAssertEqualObjects(user.metadata.creationDate,
                          [NSDate dateWithTimeIntervalSince1970:kCreationDateTimeIntervalInSeconds]);
    XCTAssertEqualObjects(user.metadata.lastSignInDate,
                          [NSDate dateWithTimeIntervalSince1970:kLastSignInDateTimeIntervalInSeconds]);

    // Verify FIRUser properties besides providerData contents.
    XCTAssertFalse(user.anonymous);
    XCTAssertTrue(user.emailVerified);
    XCTAssertEqualObjects(user.refreshToken, kRefreshToken);
    XCTAssertEqual(user.providerData.count, providerUserInfos.count);

    NSDictionary<NSString *, id<FIRUserInfo>> *providerMap =
        [self dictionaryWithUserInfoArray:user.providerData];

    // Verify FIRUserInfo properties from email/password.
    id<FIRUserInfo> passwordUserInfo = providerMap[FIREmailAuthProviderID];
    XCTAssertEqualObjects(passwordUserInfo.uid, kEmail);
    XCTAssertNil(passwordUserInfo.displayName);
    XCTAssertNil(passwordUserInfo.photoURL);
    XCTAssertEqualObjects(passwordUserInfo.email, kEmail);

    // Verify FIRUserInfo properties from the Google auth provider.
    id<FIRUserInfo> googleUserInfo = providerMap[FIRGoogleAuthProviderID];
    XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID);
    XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName);
    XCTAssertEqualObjects(googleUserInfo.photoURL, [NSURL URLWithString:kGooglePhotoURL]);
    XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);

    // Verify FIRUserInfo properties from the Facebook auth provider.
    id<FIRUserInfo> facebookUserInfo = providerMap[FIRFacebookAuthProviderID];
    XCTAssertEqualObjects(facebookUserInfo.uid, kFacebookID);
    XCTAssertNil(facebookUserInfo.displayName);
    XCTAssertNil(facebookUserInfo.photoURL);
    XCTAssertEqualObjects(facebookUserInfo.email, kFacebookEmail);

    #if TARGET_OS_IOS
    // Verify FIRUserInfo properties from the phone auth provider.
    id<FIRUserInfo> phoneUserInfo = providerMap[FIRPhoneAuthProviderID];
    XCTAssertEqualObjects(phoneUserInfo.phoneNumber, kPhoneNumber);
    #endif

    // Test NSSecureCoding
    XCTAssertTrue([FIRUser supportsSecureCoding]);
    NSMutableData *data = [NSMutableData data];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:user forKey:kUserArchiverKey];
    [archiver finishEncoding];

    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    FIRUser *unarchivedUser = [unarchiver decodeObjectForKey:kUserArchiverKey];

    // Verify NSSecureCoding for FIRUser
    XCTAssertEqualObjects(unarchivedUser.providerID, user.providerID);
    XCTAssertEqualObjects(unarchivedUser.uid, user.uid);
    XCTAssertEqualObjects(unarchivedUser.email, user.email);
    XCTAssertEqualObjects(unarchivedUser.photoURL, user.photoURL);
    XCTAssertEqualObjects(unarchivedUser.displayName, user.displayName);

    // Verify NSSecureCoding properties besides providerData contents.
    XCTAssertEqual(unarchivedUser.anonymous, user.anonymous);
    XCTAssertEqual(unarchivedUser.emailVerified, user.emailVerified);
    XCTAssertEqualObjects(unarchivedUser.refreshToken, user.refreshToken);
    XCTAssertEqualObjects(unarchivedUser.metadata.creationDate, user.metadata.creationDate);
    XCTAssertEqualObjects(unarchivedUser.metadata.lastSignInDate, user.metadata.lastSignInDate);
    XCTAssertEqual(unarchivedUser.providerData.count, user.providerData.count);

    NSDictionary<NSString *, id<FIRUserInfo>> *unarchivedProviderMap =
        [self dictionaryWithUserInfoArray:unarchivedUser.providerData];

    // Verify NSSecureCoding properties from email/password.
    id<FIRUserInfo> unarchivedPasswordUserInfo = unarchivedProviderMap[FIREmailAuthProviderID];
    XCTAssertEqualObjects(unarchivedPasswordUserInfo.uid, passwordUserInfo.uid);
    XCTAssertEqualObjects(unarchivedPasswordUserInfo.displayName, passwordUserInfo.displayName);
    XCTAssertEqualObjects(unarchivedPasswordUserInfo.photoURL, passwordUserInfo.photoURL);
    XCTAssertEqualObjects(unarchivedPasswordUserInfo.email, passwordUserInfo.email);

    // Verify NSSecureCoding properties from the Google auth provider.
    id<FIRUserInfo> unarchivedGoogleUserInfo = unarchivedProviderMap[FIRGoogleAuthProviderID];
    XCTAssertEqualObjects(unarchivedGoogleUserInfo.uid, googleUserInfo.uid);
    XCTAssertEqualObjects(unarchivedGoogleUserInfo.displayName, googleUserInfo.displayName);
    XCTAssertEqualObjects(unarchivedGoogleUserInfo.photoURL, googleUserInfo.photoURL);
    XCTAssertEqualObjects(unarchivedGoogleUserInfo.email, googleUserInfo.email);

    // Verify NSSecureCoding properties from the Facebook auth provider.
    id<FIRUserInfo> unarchivedFacebookUserInfo = unarchivedProviderMap[FIRFacebookAuthProviderID];
    XCTAssertEqualObjects(unarchivedFacebookUserInfo.uid, facebookUserInfo.uid);
    XCTAssertEqualObjects(unarchivedFacebookUserInfo.displayName, facebookUserInfo.displayName);
    XCTAssertEqualObjects(unarchivedFacebookUserInfo.photoURL, facebookUserInfo.photoURL);
    XCTAssertEqualObjects(unarchivedFacebookUserInfo.email, facebookUserInfo.email);

    #if TARGET_OS_IOS
    // Verify FIRUserInfo properties from the phone auth provider.
    id<FIRUserInfo> unarchivedPhoneUserInfo = unarchivedProviderMap[FIRPhoneAuthProviderID];
    XCTAssertEqualObjects(unarchivedPhoneUserInfo.phoneNumber, phoneUserInfo.phoneNumber);
    #endif

    [expectation fulfill];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdateEmailSuccess
    @brief Tests the flow of a successful @c updateEmail:completion: call.
 */
- (void)testUpdateEmailSuccess {
  id (^mockUserInfoWithDisplayName)(NSString *) = ^(NSString *displayName) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    return mockGetAccountInfoResponseUser;
  };
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName);
  [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    // Pretend that the display name on the server has been changed since last request.
    [self
        expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoWithDisplayName(kNewDisplayName)];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertEqualObjects(request.email, kNewEmail);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.displayName);
      XCTAssertNil(request.photoURL);
      XCTAssertNil(request.password);
      XCTAssertNil(request.providers);
      XCTAssertNil(request.deleteAttributes);
      XCTAssertNil(request.deleteProviders);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail);
        OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
        callback(mockSetAccountInfoResponse, nil);
      });
    });
    [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
      XCTAssertNil(error);
      XCTAssertEqualObjects(user.email, kNewEmail);
      XCTAssertEqualObjects(user.displayName, kNewDisplayName);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdateEmailWithAuthLinkAccountSuccess
    @brief Tests a successful @c updateEmail:completion: call updates provider info.
 */
- (void)testUpdateEmailWithAuthLinkAccountSuccess {
  id (^mockUserInfoWithDisplayName)(NSString *) = ^(NSString *displayName) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    return mockGetAccountInfoResponseUser;
  };
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName);
  [self signInWithEmailLinkWithMockUserInfoResponse:userInfoResponse
                                         completion:^(FIRUser *user) {
    // Pretend that the display name on the server has been changed since last request.
    [self
        expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoWithDisplayName(kNewDisplayName)];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
    .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                     FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertEqualObjects(request.email, kNewEmail);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.displayName);
      XCTAssertNil(request.photoURL);
      XCTAssertNil(request.password);
      XCTAssertNil(request.providers);
      XCTAssertNil(request.deleteAttributes);
      XCTAssertNil(request.deleteProviders);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail);
        OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
        callback(mockSetAccountInfoResponse, nil);
      });
    });
    [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
      XCTAssertNil(error);
      XCTAssertEqualObjects(user.email, kNewEmail);
      XCTAssertEqualObjects(user.displayName, kNewDisplayName);
      XCTAssertFalse(user.isAnonymous);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdateEmailFailure
    @brief Tests the flow of a failed @c updateEmail:completion: call.
 */
- (void)testUpdateEmailFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils invalidEmailErrorWithMessage:nil]);
    [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidEmail);
      // Email should not have changed on the client side.
      XCTAssertEqualObjects(user.email, kEmail);
      // User is still signed in.
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdateEmailAutoSignOut
    @brief Tests the flow of a failed @c updateEmail:completion: call that automatically signs out.
 */
- (void)testUpdateEmailAutoSignOut {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils invalidUserTokenErrorWithMessage:nil]);
    [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidUserToken);
      // Email should not have changed on the client side.
      XCTAssertEqualObjects(user.email, kEmail);
      // User is no longer signed in.
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

#if TARGET_OS_IOS
/** @fn testUpdatePhoneSuccess
    @brief Tests the flow of a successful @c updatePhoneNumberCredential:completion: call.
 */
- (void)testUpdatePhoneSuccess {
  id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    if (phoneNumber.length) {
      OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
    }
    return mockGetAccountInfoResponseUser;
  };

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
  [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber isLinkOperation:NO error:nil];
    id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber);
    [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate];

    FIRPhoneAuthCredential *credential =
      [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                   verificationCode:kVerificationCode];
    [user updatePhoneNumberCredential:credential
                           completion:^(NSError * _Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdatePhoneNumberFailure
    @brief Tests the flow of a failed @c updatePhoneNumberCredential:completion: call.
 */
- (void)testUpdatePhoneNumberFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]);
    FIRPhoneAuthCredential *credential =
        [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                     verificationCode:kVerificationCode];
    [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber);
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdatePhoneNumberFailureAutoSignOut
    @brief Tests the flow of a failed @c updatePhoneNumberCredential:completion: call that
        automatically signs out.
 */
- (void)testUpdatePhoneNumberFailureAutoSignOut {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]);
    FIRPhoneAuthCredential *credential =
        [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                     verificationCode:kVerificationCode];
    [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired);
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}
#endif

/** @fn testUpdatePasswordSuccess
    @brief Tests the flow of a successful @c updatePassword:completion: call.
 */
- (void)testUpdatePasswordSuccess {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertEqualObjects(request.password, kNewPassword);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.displayName);
      XCTAssertNil(request.photoURL);
      XCTAssertNil(request.email);
      XCTAssertNil(request.providers);
      XCTAssertNil(request.deleteAttributes);
      XCTAssertNil(request.deleteProviders);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
        callback(mockSetAccountInfoResponse, nil);
      });
    });
    [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertFalse(user.isAnonymous);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdatePasswordFailure
    @brief Tests the flow of a failed @c updatePassword:completion: call.
 */
- (void)testUpdatePasswordFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils requiresRecentLoginErrorWithMessage:nil]);
    [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeRequiresRecentLogin);
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUpdateEmptyPasswordFailure
    @brief Tests the flow of a failed @c updatePassword:completion: call due to an empty password.
 */
- (void)testUpdateEmptyPasswordFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [user updatePassword:@"" completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
}

/** @fn testUpdatePasswordFailureAutoSignOut
    @brief Tests the flow of a failed @c updatePassword:completion: call that automatically signs
        out.
 */
- (void)testUpdatePasswordFailureAutoSignOut {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
    [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testChangeProfileSuccess
    @brief Tests a successful user profile change flow.
 */
- (void)testChangeProfileSuccess {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser photoURL]).andReturn(kPhotoURL);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertEqualObjects(request.displayName, kNewDisplayName);
      XCTAssertEqualObjects(request.photoURL, [NSURL URLWithString:kNewPhotoURL]);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.email);
      XCTAssertNil(request.password);
      XCTAssertNil(request.providers);
      XCTAssertNil(request.deleteAttributes);
      XCTAssertNil(request.deleteProviders);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
        callback(mockSetAccountInfoResponse, nil);
      });
    });
    FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest];
    profileChange.photoURL = [NSURL URLWithString:kNewPhotoURL];
    profileChange.displayName = kNewDisplayName;
    [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(user.displayName, kNewDisplayName);
      XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kNewPhotoURL]);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testChangeProfileFailure
    @brief Tests a failed user profile change flow.
 */
- (void)testChangeProfileFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
    FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest];
    profileChange.displayName = kNewDisplayName;
    [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
      XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testChangeProfileFailureAutoSignOut
    @brief Tests a failed user profile change flow that automatically signs out.
 */
- (void)testChangeProfileFailureAutoSignOut {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils userNotFoundErrorWithMessage:nil]);
    FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest];
    profileChange.displayName = kNewDisplayName;
    [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserNotFound);
      XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReloadSuccess
    @brief Tests the flow of a successful @c reloadWithCompletion: call.
 */
- (void)testReloadSuccess {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    id mockGetAccountInfoResponseUserNew = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUserNew localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUserNew email]).andReturn(kNewEmail);
    OCMStub([mockGetAccountInfoResponseUserNew displayName]).andReturn(kNewDisplayName);
    OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash);
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUserNew];
    [user reloadWithCompletion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(user.email, kNewEmail);
      XCTAssertEqualObjects(user.displayName, kNewDisplayName);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testGetIDTokenResultSuccess
    @brief Tests the flow of a successful @c getIDTokenResultWithCompletion: call.
 */
- (void)testGetIDTokenResultSuccess {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id mockSecureTokenService = OCMClassMock([FIRSecureTokenService class]);
  OCMStub([mockSecureTokenService hasValidAccessToken]).andReturn(YES);
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    [user getIDTokenResultWithCompletion:^(FIRAuthTokenResult *_Nullable tokenResult,
                                           NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(tokenResult.token, kAccessToken);
      XCTAssertTrue(tokenResult.issuedAtDate &&
          [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.expirationDate &&
          [tokenResult.expirationDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testGetIDTokenResultForcingRefreshFailure
    @brief Tests the flow successful @c getIDTokenResultForcingRefresh:completion: calls.
 */
- (void)testGetIDTokenResultForcingRefreshSuccess {
  [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessToken];
  [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength415];
  [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength416];
  [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength523];
  [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenWithBase64URLCharacter];
}

/** @fn testGetIDTokenResultSuccessWithBase64EncodedURL
    @brief Tests the flow of a successful @c getIDTokenResultWithCompletion: call using a base64 url
        encoded string.
 */
 - (void)testGetIDTokenResultSuccessWithBase64EncodedURL {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
                         FIRSecureTokenResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);

      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]);
        OCMStub([mockSecureTokenResponse accessToken]).andReturn(kAccessTokenWithBase64URLCharacter);
        callback(mockSecureTokenResponse, nil);
      });
    });
    [user getIDTokenResultForcingRefresh:YES
                              completion:^(FIRAuthTokenResult *_Nullable tokenResult,
                                           NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(tokenResult.token, kAccessTokenWithBase64URLCharacter);
      XCTAssertTrue(tokenResult.issuedAtDate &&
          [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.expirationDate &&
          [tokenResult.expirationDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]);
      NSDictionary *claims = tokenResult.claims;
      XCTAssertEqualObjects(claims[@"email"], kbase64URLEncodedEmail);
      XCTAssertEqualObjects(claims[@"aud"], kbase64URLEncodedAUD);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testGetIDTokenResultForcingRefreshFailure
    @brief Tests the flow of a failed @c getIDTokenResultForcingRefresh:completion: call.
 */
- (void)testGetIDTokenResultForcingRefreshFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
                         FIRSecureTokenResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);

      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil];
        callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]);
      });
    });
    [user getIDTokenResultForcingRefresh:YES
                              completion:^(FIRAuthTokenResult *_Nullable tokenResult,
                                           NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(tokenResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReloadFailure
    @brief Tests the flow of a failed @c reloadWithCompletion: call.
 */
- (void)testReloadFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils quotaExceededErrorWithMessage:nil]);
    [user reloadWithCompletion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeQuotaExceeded);
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReloadFailureAutoSignOut
    @brief Tests the flow of a failed @c reloadWithCompletion: call that automtatically signs out.
 */
- (void)testReloadFailureAutoSignOut {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]);
    [user reloadWithCompletion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired);
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReauthenticateSuccess
    @brief Tests the flow of a successful @c reauthenticateWithCredential:completion: call.
 */
- (void)testReauthenticateSuccess {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
                         FIRVerifyPasswordResponseCallback callback) {
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
        // New authentication comes back with new access token.
        OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kNewAccessToken);
        OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
            .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
        OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
            callback(mockVeriyPasswordResponse, nil);
      });
    });
    OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
                         FIRGetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      // Verify that the new access token is being used for subsequent requests.
      XCTAssertEqualObjects(request.accessToken, kNewAccessToken);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
        OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
        callback(mockGetAccountInfoResponse, nil);
      });
    });
    FIRAuthCredential *emailCredential =
        [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];
    [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      // Verify that the current user is unchanged.
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReauthenticateAndRetrieveDataSuccess
    @brief Tests the flow of a successful @c reauthenticateAndRetrieveDataWithCredential:completion:
        call.
 */
- (void)testReauthenticateAndRetrieveDataSuccess {
  [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
                         federatedID:kGoogleID
                         displayName:kGoogleDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kGoogleIDToken
                 providerAccessToken:kGoogleAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *googleCredential =
      [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:googleCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserGoogle:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID);
    XCTAssertNil(error);

    [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
                           federatedID:kGoogleID
                           displayName:kGoogleDisplayName
                               profile:[[self class] googleProfile]
                       providerIDToken:kGoogleIDToken
                   providerAccessToken:kGoogleAccessToken];

    FIRAuthCredential *reauthenticateGoogleCredential =
      [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [authResult.user
        reauthenticateAndRetrieveDataWithCredential:reauthenticateGoogleCredential
                                         completion:^(FIRAuthDataResult *_Nullable
                                                          reauthenticateAuthResult,
                                                      NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      // Verify that the current user is unchanged.
      XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
      // Verify that the current user and reauthenticated user are not same pointers.
      XCTAssertNotEqualObjects(authResult.user, reauthenticateAuthResult.user);
      // Verify that anyway the current user and reauthenticated user have same IDs.
      XCTAssertEqualObjects(authResult.user.uid, reauthenticateAuthResult.user.uid);
      XCTAssertEqualObjects(authResult.user.displayName, reauthenticateAuthResult.user.displayName);
      XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.profile,
                            [[self class] googleProfile]);
      XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.username, kUserName);
      XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.providerID,
                            FIRGoogleAuthProviderID);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  [self assertUserGoogle:[FIRAuth auth].currentUser];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReauthenticateFailure
    @brief Tests the flow of a failed @c reauthenticateWithCredential:completion: call.
 */
- (void)testReauthenticateFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
                         FIRVerifyPasswordResponseCallback callback) {
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
        OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kNewAccessToken);
        OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
            .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
        OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
            callback(mockVeriyPasswordResponse, nil);
      });
    });
    OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
                         FIRGetAccountInfoResponseCallback callback) {
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockGetAccountInfoResponseUserNew = OCMClassMock([FIRGetAccountInfoResponseUser class]);
        // The newly-signed-in user has a different ID.
        OCMStub([mockGetAccountInfoResponseUserNew localID]).andReturn(kAnotherLocalID);
        OCMStub([mockGetAccountInfoResponseUserNew email]).andReturn(kNewEmail);
        OCMStub([mockGetAccountInfoResponseUserNew displayName]).andReturn(kNewDisplayName);
        OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash);
        id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
        OCMStub([mockGetAccountInfoResponse users])
            .andReturn(@[ mockGetAccountInfoResponseUserNew ]);
        callback(mockGetAccountInfoResponse, nil);
      });
    });
    FIRAuthCredential *emailCredential =
        [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];
    [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      // Verify user mismatch error.
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch);
      // Verify that the current user is unchanged.
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testReauthenticateUserMismatchFailure
    @brief Tests the flow of a failed @c reauthenticateWithCredential:completion: call due to trying
        to reauthenticate a user that does not exist.
 */
- (void)testReauthenticateUserMismatchFailure {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
                         FIRVerifyAssertionResponseCallback callback) {
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
            callback(nil, [FIRAuthErrorUtils userNotFoundErrorWithMessage:nil]);
      });
    });
    FIRAuthCredential *googleCredential =
      [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [user reauthenticateWithCredential:googleCredential completion:^(NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      // Verify user mismatch error.
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch);
      // Verify that the current user is unchanged.
      XCTAssertEqual([FIRAuth auth].currentUser, user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkAndRetrieveDataSuccess
    @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
        call.
 */
- (void)testlinkAndRetrieveDataSuccess {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
                           federatedID:kGoogleID
                           displayName:kGoogleDisplayName
                               profile:[[self class] googleProfile]
                       providerIDToken:kGoogleIDToken
                   providerAccessToken:kGoogleAccessToken];

    FIRAuthCredential *linkGoogleCredential =
      [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                            linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      // Verify that the current user is unchanged.
      XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
      // Verify that the current user and reauthenticated user are same pointers.
      XCTAssertEqualObjects(authResult.user, linkAuthResult.user);
      // Verify that anyway the current user and linked user have same IDs.
      XCTAssertEqualObjects(authResult.user.uid, linkAuthResult.user.uid);
      XCTAssertEqualObjects(authResult.user.displayName, linkAuthResult.user.displayName);
      XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.profile,
                            [[self class] googleProfile]);
      XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.username, kUserName);
      XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.providerID,
                            FIRGoogleAuthProviderID);
      [expectation fulfill];
    }];

  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  [self assertUserGoogle:[FIRAuth auth].currentUser];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkAndRetrieveDataError
    @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
        call with an error from the backend.
 */
- (void)testlinkAndRetrieveDataError {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
                       FIRVerifyAssertionResponseCallback callback) {
        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
            callback(nil,
                     [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:kEmail]);
      });
    });

    FIRAuthCredential *linkGoogleCredential =
        [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                            linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(linkAuthResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential);
      XCTAssertEqual(error.userInfo[FIRAuthErrorUserInfoEmailKey], kEmail);
      XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkAndRetrieveDataProviderAlreadyLinked
    @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
        call with FIRAuthErrorCodeProviderAlreadyLinked, which is a client side error.
 */
- (void)testlinkAndRetrieveDataProviderAlreadyLinked {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    FIRAuthCredential *linkFacebookCredential =
        [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
    [authResult.user linkAndRetrieveDataWithCredential:linkFacebookCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                            linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(linkAuthResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
      XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkAndRetrieveDataErrorAutoSignOut
    @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
        call that automatically signs out.
 */
- (void)testlinkAndRetrieveDataErrorAutoSignOut {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
                       FIRVerifyAssertionResponseCallback callback) {
        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
            callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
      });
    });

    FIRAuthCredential *linkGoogleCredential =
        [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                            linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(linkAuthResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testLinkingAnonymousAccountsUpdatesIsAnonymous
    @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
        invocation for email credential.
 */
- (void)testLinkingAnonymousAccountsUpdatesIsAnonymous {
  FIRAuthCredential *linkEmailCredential =
      [FIREmailAuthProvider credentialWithEmail:kEmail
                                           link:@"https://google.com?oobCode=aCode&mode=signIn"];

  id (^mockUserInfoWithDisplayName)(NSString *, BOOL) = ^(NSString *displayName,
                                                          BOOL hasProviders) {
    NSArray *providers = hasProviders ? @[ @{
      @"providerId": FIREmailAuthProviderID,
      @"email": kEmail
      } ] : @[];
    FIRGetAccountInfoResponseUser *responseUser =
        [[FIRGetAccountInfoResponseUser alloc] initWithDictionary:@{
          @"providerUserInfo": providers,
          @"localId": kLocalID,
          @"displayName": displayName,
          @"email": kEmail
        }];
    return responseUser;
  };
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName, NO);

  [self signInAnonymouslyWithMockGetAccountInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    // Pretend that the display name and providers on the server have been updated.
    // Get account info is expected to be invoked twice.
    id updatedMockUser = mockUserInfoWithDisplayName(kNewDisplayName, YES);
    [self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser];
    [self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser];
    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
      OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail);
      OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
      callback(mockSetAccountInfoResponse, nil);
    });
    XCTAssertTrue(user.isAnonymous);

    [user linkAndRetrieveDataWithCredential:linkEmailCredential
                                 completion:^(FIRAuthDataResult *_Nullable linkAuthResult,
                                              NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(user.email, kEmail);
      XCTAssertFalse(user.isAnonymous);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkEmailAndRetrieveDataSuccess
    @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
        invocation for email credential.
 */
- (void)testlinkEmailAndRetrieveDataSuccess {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kEmailDisplayName);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    // Get account info is expected to be invoked twice.
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];

    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertEqualObjects(request.password, kFakePassword);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.displayName);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        callback(mockSetAccountInfoResponse, nil);
      });
    });

    FIRAuthCredential *linkEmailCredential =
        [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];
    [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                             linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(linkAuthResult.user.email, kEmail);
      XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName);
      [expectation fulfill];
    }];

  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkEmailProviderAlreadyLinkedError
    @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
        invocation for email credential and FIRAuthErrorCodeProviderAlreadyLinked which is a client
        side error.
 */
- (void)testlinkEmailProviderAlreadyLinkedError {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kEmailDisplayName);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    // Get account info is expected to be invoked twice.
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
    [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];

    OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertEqualObjects(request.password, kFakePassword);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.displayName);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        callback(mockSetAccountInfoResponse, nil);
      });
    });

    FIRAuthCredential *linkEmailCredential =
        [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];
    [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                             linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertNil(error);
      XCTAssertEqualObjects(linkAuthResult.user.email, kEmail);
      XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName);

      // Try linking same credential a second time to trigger client side error.
      [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
                                              completion:^(FIRAuthDataResult *_Nullable
                                                              linkAuthResult,
                                                           NSError *_Nullable error) {
        XCTAssertTrue([NSThread isMainThread]);
        XCTAssertNil(linkAuthResult);
        XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
        [expectation fulfill];
      }];
    }];

  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkEmailAndRetrieveDataError
    @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
        invocation for email credential and an error from the backend.
 */
- (void)testlinkEmailAndRetrieveDataError {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
                         FIRGetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        callback(nil, [FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
      });
    });

    FIRAuthCredential *linkEmailCredential =
        [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];
    [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                            linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(linkAuthResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
      XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkEmailAndRetrieveDataError
    @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
        invocation that automatically signs out.
 */
- (void)testlinkEmailAndRetrieveDataErrorAutoSignOut {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
                         FIRGetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        callback(nil, [FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]);
      });
    });

    FIRAuthCredential *linkEmailCredential =
        [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];
    [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
                                            completion:^(FIRAuthDataResult *_Nullable
                                                            linkAuthResult,
                                                         NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(linkAuthResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired);
      XCTAssertNil([FIRAuth auth].currentUser);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkCredentialSuccess
    @brief Tests the flow of a successful @c linkWithCredential:completion: call, without additional
        IDP data.
 */
- (void)testlinkCredentialSuccess {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
                           federatedID:kGoogleID
                           displayName:kGoogleDisplayName
                               profile:[[self class] googleProfile]
                       providerIDToken:kGoogleIDToken
                   providerAccessToken:kGoogleAccessToken];

    FIRAuthCredential *linkGoogleCredential =
      [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [authResult.user linkWithCredential:linkGoogleCredential
                             completion:^(FIRUser *_Nullable user,
                                          NSError *_Nullable error) {
      XCTAssertNil(error);
      id<FIRUserInfo> userInfo = user.providerData.firstObject;
      XCTAssertEqual(userInfo.providerID, FIRGoogleAuthProviderID);
      XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  [self assertUserGoogle:[FIRAuth auth].currentUser];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkCredentialError
    @brief Tests the flow of an unsuccessful @c linkWithCredential:completion: call, with an error
        from the backend.
 */
- (void)testlinkCredentialError {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
                       FIRVerifyAssertionResponseCallback callback) {
        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
            callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
      });
    });

    FIRAuthCredential *linkGoogleCredential =
      [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
    [authResult.user linkWithCredential:linkGoogleCredential
                             completion:^(FIRUser *_Nullable user,
                                          NSError *_Nullable error) {
      XCTAssertNil(user);
      XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkCredentialProviderAlreadyLinkedError
    @brief Tests the flow of an unsuccessful @c linkWithCredential:completion: call, with a client
        side error.
 */
- (void)testlinkCredentialProviderAlreadyLinkedError {
  [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
                         federatedID:kFacebookID
                         displayName:kFacebookDisplayName
                             profile:[[self class] googleProfile]
                     providerIDToken:kFacebookIDToken
                 providerAccessToken:kFacebookAccessToken];

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [[FIRAuth auth] signOut:NULL];
  FIRAuthCredential *facebookCredential =
      [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
  [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
                                           completion:^(FIRAuthDataResult *_Nullable authResult,
                                                        NSError *_Nullable error) {
    XCTAssertTrue([NSThread isMainThread]);
    [self assertUserFacebook:authResult.user];
    XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
    XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
    XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
    XCTAssertNil(error);

    FIRAuthCredential *linkFacebookCredential =
        [FIRFacebookAuthProvider credentialWithAccessToken:kGoogleAccessToken];
    [authResult.user linkWithCredential:linkFacebookCredential
                             completion:^(FIRUser *_Nullable user,
                                          NSError *_Nullable error) {
      XCTAssertNil(user);
      XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

#if TARGET_OS_IOS
/** @fn testlinkPhoneAuthCredentialSuccess
    @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
        call using a phoneAuthCredential.
 */
- (void)testlinkPhoneAuthCredentialSuccess {
  id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    if (phoneNumber.length) {
      NSDictionary *userInfoDictionary = @{ @"providerId" : FIRPhoneAuthProviderID };
      FIRGetAccountInfoResponseProviderUserInfo *userInfo =
          [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:userInfoDictionary];
      OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(@[ userInfo ]);
      OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
    }
    return mockGetAccountInfoResponseUser;
  };

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
  [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber isLinkOperation:YES error:nil];
    id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber);
    [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate];

    FIRPhoneAuthCredential *credential =
        [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                     verificationCode:kVerificationCode];
    [user linkAndRetrieveDataWithCredential:credential
                                 completion:^(FIRAuthDataResult *_Nullable
                                              linkAuthResult,
                                              NSError *_Nullable error) {
      XCTAssertNil(error);
      XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID,
                            FIRPhoneAuthProviderID);
      XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testUnlinkPhoneAuthCredentialSuccess
    @brief Tests the flow of a successful @c unlinkFromProvider:completion: call using a
        @c FIRPhoneAuthProvider.
 */
- (void)testUnlinkPhoneAuthCredentialSuccess {
  id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    if (phoneNumber.length) {
      NSDictionary *userInfoDictionary = @{ @"providerId" : FIRPhoneAuthProviderID };
      FIRGetAccountInfoResponseProviderUserInfo *userInfo =
          [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:userInfoDictionary];
      OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(@[ userInfo ]);
      OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
    }
    return mockGetAccountInfoResponseUser;
  };

  OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
                         FIRSetAccountInfoResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      XCTAssertNotNil(request.deleteProviders);
      XCTAssertNil(request.email);
      XCTAssertNil(request.localID);
      XCTAssertNil(request.displayName);
      XCTAssertNil(request.photoURL);
      XCTAssertNil(request.password);
      XCTAssertNil(request.providers);
      XCTAssertNil(request.deleteAttributes);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
        callback(mockSetAccountInfoResponse, nil);
      });
  });
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
  [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber isLinkOperation:YES error:nil];
    id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber);
    [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate];

    FIRPhoneAuthCredential *credential =
        [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                     verificationCode:kVerificationCode];
    // Link phone credential.
    [user linkAndRetrieveDataWithCredential:credential
                                 completion:^(FIRAuthDataResult *_Nullable
                                              linkAuthResult,
                                              NSError *_Nullable error) {
      XCTAssertNil(error);
      XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID,
                            FIRPhoneAuthProviderID);
      XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber);
      // Immediately unlink the phone auth provider.
      [user unlinkFromProvider:FIRPhoneAuthProviderID
                    completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
        XCTAssertNil(error);
        XCTAssertNil([FIRAuth auth].currentUser.phoneNumber);
        [expectation fulfill];
      }];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkPhoneAuthCredentialFailure
    @brief Tests the flow of a failed call to @c linkAndRetrieveDataWithCredential:completion: due
        to a phone provider already being linked.
 */
- (void)testlinkPhoneAuthCredentialFailure {
  id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    if (phoneNumber.length) {
      OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
    }
    return mockGetAccountInfoResponseUser;
  };

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
  [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    NSError *error = [FIRAuthErrorUtils providerAlreadyLinkedError];
    [self expectVerifyPhoneNumberRequestWithPhoneNumber:nil isLinkOperation:YES error:error];
    FIRPhoneAuthCredential *credential =
        [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                     verificationCode:kVerificationCode];
    [user linkAndRetrieveDataWithCredential:credential
                                 completion:^(FIRAuthDataResult *_Nullable
                                              linkAuthResult,
                                              NSError *_Nullable error) {
      XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn testlinkPhoneCredentialAlreadyExistsError
    @brief Tests the flow of @c linkAndRetrieveDataWithCredential:completion:
        call using a phoneAuthCredential and a credential already exisits error. In this case we
        should get a FIRAuthCredential in the error object.
 */
- (void)testlinkPhoneCredentialAlreadyExistsError {
  id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
    id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
    OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
    OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
    OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
    if (phoneNumber.length) {
      OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
    }
    return mockGetAccountInfoResponseUser;
  };

   void (^expectVerifyPhoneNumberRequest)(NSString *) = ^(NSString *phoneNumber) {
    OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request,
                         FIRVerifyPhoneNumberResponseCallback callback) {
      XCTAssertEqualObjects(request.verificationID, kVerificationID);
      XCTAssertEqualObjects(request.verificationCode, kVerificationCode);
      XCTAssertEqualObjects(request.accessToken, kAccessToken);
      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        FIRPhoneAuthCredential *credential =
            [[FIRPhoneAuthCredential alloc] initWithTemporaryProof:kTemporaryProof
                                                       phoneNumber:kPhoneNumber
                                                        providerID:FIRPhoneAuthProviderID];
        callback(nil,
                 [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:nil
                                                                credential:credential]);
      });
    });
  };

  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
  [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
                                             completion:^(FIRUser *user) {
    expectVerifyPhoneNumberRequest(kPhoneNumber);

    FIRPhoneAuthCredential *credential =
        [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
                                                     verificationCode:kVerificationCode];
    [user linkAndRetrieveDataWithCredential:credential
                                 completion:^(FIRAuthDataResult *_Nullable
                                              linkAuthResult,
                                              NSError *_Nullable error) {
      XCTAssertNil(linkAuthResult);
      XCTAssertEqual(error.code, FIRAuthErrorCodeCredentialAlreadyInUse);
      FIRPhoneAuthCredential *credential = error.userInfo[FIRAuthUpdatedCredentialKey];
      XCTAssertEqual(credential.temporaryProof, kTemporaryProof);
      XCTAssertEqual(credential.phoneNumber, kPhoneNumber);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}
#endif

#pragma mark - Helpers

/** @fn getIDTokenResultForcingRefreshSuccess
    @brief Helper for testing the flow of a successful @c
        getIDTokenResultForcingRefresh:completion: call.
 */
- (void)getIDTokenResultForcingRefreshSuccessWithIDToken:(NSString *)idToken {
  id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
  OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
                                             completion:^(FIRUser *user) {
    OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
        .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
                         FIRSecureTokenResponseCallback callback) {
      XCTAssertEqualObjects(request.APIKey, kAPIKey);

      dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
        id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]);
        OCMStub([mockSecureTokenResponse accessToken]).andReturn(idToken);
        callback(mockSecureTokenResponse, nil);
      });
    });
    [user getIDTokenResultForcingRefresh:YES
                              completion:^(FIRAuthTokenResult *_Nullable tokenResult,
                                           NSError *_Nullable error) {
      XCTAssertTrue([NSThread isMainThread]);
      XCTAssertNil(error);
      XCTAssertEqualObjects(tokenResult.token, idToken);
      XCTAssertTrue(tokenResult.issuedAtDate &&
          [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.expirationDate &&
          [tokenResult.expirationDate isKindOfClass:[NSDate class]]);
      XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]);
      [expectation fulfill];
    }];
  }];
  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  OCMVerifyAll(_mockBackend);
}

/** @fn signInAnonymouslyWithMockGetAccountInfoResponse:completion:
    @brief Signs in with an anonymous account with mocked backend end calls.
    @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
    @param completion The completion block that takes the newly signed-in user as the only
        parameter.
 */
- (void)signInAnonymouslyWithMockGetAccountInfoResponse:(id)mockUserInfoResponse
                                             completion:(void (^)(FIRUser *user))completion {
  OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
                       FIRSignupNewUserCallback callback) {
    id mockSignUpResponse = OCMClassMock([FIRSignUpNewUserResponse class]);
    OCMStub([mockSignUpResponse IDToken]).andReturn(kAccessToken);
    OCMStub([mockSignUpResponse approximateExpirationDate])
        .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
    OCMStub([mockSignUpResponse refreshToken]).andReturn(kRefreshToken);
    callback(mockSignUpResponse, nil);
  });
  [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
  [[FIRAuth auth] signOut:NULL];
  [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRAuthDataResult *_Nullable result,
                                                    NSError *_Nullable error) {
    XCTAssertNotNil(result.user);
    XCTAssertNil(error);
    completion(result.user);
  }];
}

/** @fn signInWithEmailPasswordWithMockGetAccountInfoResponse:completion:
    @brief Signs in with an email and password account with mocked backend end calls.
    @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
    @param completion The completion block that takes the newly signed-in user as the only
        parameter.
 */
- (void)signInWithEmailPasswordWithMockUserInfoResponse:(id)mockUserInfoResponse
                                             completion:(void (^)(FIRUser *user))completion {
  OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
                       FIRVerifyPasswordResponseCallback callback) {
    dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
      id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
      OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kAccessToken);
      OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
          .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
      OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
      callback(mockVeriyPasswordResponse, nil);
    });
  });
  [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
  [[FIRAuth auth] signOut:NULL];
  [[FIRAuth auth] signInWithEmail:kEmail
                         password:kFakePassword
                       completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
    XCTAssertNotNil(result.user);
    XCTAssertNil(error);
    completion(result.user);
  }];
}

/** @fn signInWithEmailLinkWithMockGetAccountInfoResponse:completion:
    @brief Signs in with an email link auth account with mocked backend end calls.
    @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
    @param completion The completion block that takes the newly signed-in user as the only
        parameter.
 */
- (void)signInWithEmailLinkWithMockUserInfoResponse:(id)mockUserInfoResponse
                                         completion:(void (^)(FIRUser *user))completion {
  OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request,
                       FIREmailLinkSigninResponseCallback callback) {
    id mockVerifyLinkResponse = OCMClassMock([FIREmailLinkSignInResponse class]);
    OCMStub([mockVerifyLinkResponse IDToken]).andReturn(kAccessToken);
    OCMStub([mockVerifyLinkResponse approximateExpirationDate])
        .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
    OCMStub([mockVerifyLinkResponse refreshToken]).andReturn(kRefreshToken);
    callback(mockVerifyLinkResponse, nil);
  });
  [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
  [[FIRAuth auth] signOut:NULL];
  [[FIRAuth auth] signInWithEmail:kEmail
                             link:@"https://www.google.com?oobCode=aCode&mode=signIn"
                       completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
    XCTAssertNotNil(result.user);
    XCTAssertNil(error);
    completion(result.user);
  }];
}

/** @fn expectGetAccountInfoWithMockUserInfoResponse:
    @brief Expects a GetAccountInfo request on the mock backend and calls back with provided
        fake account data.
    @param mockUserInfoResponse A mock @c FIRGetAccountInfoResponseUser object containing user info.
 */
- (void)expectGetAccountInfoWithMockUserInfoResponse:(id)mockUserInfoResponse {
  OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
                       FIRGetAccountInfoResponseCallback callback) {
    XCTAssertEqualObjects(request.APIKey, kAPIKey);
    XCTAssertEqualObjects(request.accessToken, kAccessToken);
    dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
      id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
      OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockUserInfoResponse ]);
      callback(mockGetAccountInfoResponse, nil);
    });
  });
}

/** @fn dictionaryWithUserInfoArray:
    @brief Converts an array of @c FIRUserInfo into a dictionary that indexed by provider IDs.
    @param userInfoArray An array of @c FIRUserInfo objects.
    @return A dictionary contains same values as @c userInfoArray does but keyed by their
        @c providerID .
 */
- (NSDictionary<NSString *, id<FIRUserInfo>> *)
    dictionaryWithUserInfoArray:(NSArray<id<FIRUserInfo>> *)userInfoArray {
  NSMutableDictionary<NSString *, id<FIRUserInfo>> *map =
      [NSMutableDictionary dictionaryWithCapacity:userInfoArray.count];
  for (id<FIRUserInfo> userInfo in userInfoArray) {
    XCTAssertNil(map[userInfo.providerID]);
    map[userInfo.providerID] = userInfo;
  }
  return map;
}

/** @fn stubSecureTokensWithMockResponse
    @brief Creates stubs on the mock response object with access and refresh tokens
    @param mockResponse The mock response object.
 */
- (void)stubTokensWithMockResponse:(id)mockResponse {
  OCMStub([mockResponse IDToken]).andReturn(kAccessToken);
  OCMStub([mockResponse approximateExpirationDate])
      .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
  OCMStub([mockResponse refreshToken]).andReturn(kRefreshToken);
}

/** @fn assertUserGoogle
    @brief Asserts the given FIRUser matching the fake data returned by
        @c expectGetAccountInfo:federatedID:displayName: .
    @param user The user object to be verified.
 */
- (void)assertUserGoogle:(FIRUser *)user {
  XCTAssertNotNil(user);
  XCTAssertEqualObjects(user.uid, kLocalID);
  XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
  XCTAssertEqual(user.providerData.count, 1u);
  id<FIRUserInfo> googleUserInfo = user.providerData[0];
  XCTAssertEqualObjects(googleUserInfo.providerID, FIRGoogleAuthProviderID);
  XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID);
  XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName);
  XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
}

/** @fn assertUserFacebook
    @brief Asserts the given FIRUser matching the fake data returned by
        @c expectGetAccountInfo:federatedID:displayName: .
    @param user The user object to be verified.
 */
- (void)assertUserFacebook:(FIRUser *)user {
  XCTAssertNotNil(user);
  XCTAssertEqualObjects(user.uid, kLocalID);
  XCTAssertEqualObjects(user.displayName, kFacebookDisplayName);
  XCTAssertEqual(user.providerData.count, 1u);
  id<FIRUserInfo> googleUserInfo = user.providerData[0];
  XCTAssertEqualObjects(googleUserInfo.providerID, FIRFacebookAuthProviderID);
  XCTAssertEqualObjects(googleUserInfo.uid, kFacebookID);
  XCTAssertEqualObjects(googleUserInfo.displayName, kFacebookDisplayName);
  XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
}

/** @fn expectGetAccountInfo:federatedID:displayName:
    @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
        data for a Google Sign-In user.
 */
- (void)expectGetAccountInfo:(NSString *)providerId
                 federatedID:(NSString *)federatedID
                 displayName:(NSString *)displayName {
  OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
                       FIRGetAccountInfoResponseCallback callback) {
    XCTAssertEqualObjects(request.APIKey, kAPIKey);
    XCTAssertEqualObjects(request.accessToken, kAccessToken);
    dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
      id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
      OCMStub([mockGoogleUserInfo providerID]).andReturn(providerId);
      OCMStub([mockGoogleUserInfo displayName]).andReturn(displayName);
      OCMStub([mockGoogleUserInfo federatedID]).andReturn(federatedID);
      OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail);
      id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
      OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
      OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName);
      OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
          .andReturn((@[ mockGoogleUserInfo ]));
      id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
      OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
      callback(mockGetAccountInfoResponse, nil);
    });
  });
}

/** @fn expectVerifyAssertionRequest:federatedID:displayName:profile:providerAccessToken:
    @brief Expects a Verify Assertion request on the mock backend and calls back with fake account
        data.
 */
- (void)expectVerifyAssertionRequest:(NSString *)providerId
                         federatedID:(NSString *)federatedID
                         displayName:(NSString *)displayName
                             profile:(NSDictionary *)profile
                     providerIDToken:(nullable NSString *)providerIDToken
                 providerAccessToken:(NSString *)providerAccessToken {
  OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
                       FIRVerifyAssertionResponseCallback callback) {
    XCTAssertEqualObjects(request.APIKey, kAPIKey);
    XCTAssertEqualObjects(request.providerID, providerId);
    XCTAssertEqualObjects(request.providerIDToken, providerIDToken);
    XCTAssertEqualObjects(request.providerAccessToken, providerAccessToken);
    XCTAssertTrue(request.returnSecureToken);
    dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
      id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
      OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(federatedID);
      OCMStub([mockVeriyAssertionResponse providerID]).andReturn(providerId);
      OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
      OCMStub([mockVeriyAssertionResponse displayName]).andReturn(displayName);
      OCMStub([mockVeriyAssertionResponse profile]).andReturn(profile);
      OCMStub([mockVeriyAssertionResponse username]).andReturn(kUserName);
      [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
      callback(mockVeriyAssertionResponse, nil);
    });
  });
  [self expectGetAccountInfo:providerId federatedID:federatedID displayName:displayName];
}

#if TARGET_OS_IOS
/** @fn expectVerifyPhoneNumberRequestWithPhoneNumber:error:
    @brief Expects a verify phone numner request on the mock backend and calls back with fake
        account data or an error.
    @param phoneNumber Optionally; The phone number to use in the mocked response.
    @param isLinkOperation Boolean value that indicates whether or not this method is triggered by
        a link operation.
    @param error Optionally; The error to return in the mocked response.
 */
- (void)expectVerifyPhoneNumberRequestWithPhoneNumber:(nullable NSString *)phoneNumber
                                       isLinkOperation:(BOOL)isLinkOperation
                                                 error:(nullable NSError*)error {
  OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
      .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request,
                     FIRVerifyPhoneNumberResponseCallback callback) {
    XCTAssertEqualObjects(request.verificationID, kVerificationID);
    XCTAssertEqualObjects(request.verificationCode, kVerificationCode);
    if (isLinkOperation) {
      XCTAssertEqual(request.operation, FIRAuthOperationTypeLink);
    } else {
      XCTAssertEqual(request.operation, FIRAuthOperationTypeUpdate);
    }
    XCTAssertEqualObjects(request.accessToken, kAccessToken);
    dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
      if (error) {
        callback(nil, error);
        return;
      }
      id mockVerifyPhoneNumberResponse = OCMClassMock([FIRVerifyPhoneNumberResponse class]);
      OCMStub([mockVerifyPhoneNumberResponse phoneNumber]).andReturn(phoneNumber);
      callback(mockVerifyPhoneNumberResponse, nil);
    });
  });
}
#endif

@end

NS_ASSUME_NONNULL_END
